package jard.webshop.management;

import jard.webshop.management.CategoryManagement;
import jard.webshop.nbp.Category;
import jard.webshop.nbp.Product;
import jard.webshop.nbp.Product_;
import jard.webshop.util.Constants;
import jard.webshop.util.ProductException;
import java.sql.SQLIntegrityConstraintViolationException;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.ParameterExpression;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import org.primefaces.model.SortOrder;

/**
 *
 * @author cjp
 */
public class ProductManagement {

    private static EntityManagerFactory emf = null;

    public static void setEntityManagerFactory(EntityManagerFactory givenEmf) {
        emf = givenEmf;
    }
    private static ProductManagement pm = null;
    private static CategoryManagement cm = null;

    public static ProductManagement getInstance() {
        if (cm == null) {
            CategoryManagement.setEntityManagerFactory(emf);
            cm = CategoryManagement.getInstance();
        }
        if (pm == null) {
            pm = new ProductManagement();
        }

        return pm;
    }

    private ProductManagement() {
    }

    public Boolean isPersisted(Product product) {
        EntityManager em = emf.createEntityManager();


        Product newProduct = null;
        try {
            newProduct = em.find(Product.class, product.getArticleNumber());
        } catch (NoResultException e) {
            return false;
        }

        return newProduct != null;
    }

    public void addProduct(Product product) {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        //System.out.println("em.contains(product) => " + em.contains(product));
        em.persist(product);
        tx.commit();
        System.out.println("Added " + product + " to DB? " + isPersisted(product));
    }

    public void saveProduct(Product product) {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        System.out.println("em.contains(product) => " + em.contains(product));
        em.merge(product);
        tx.commit();
    }

    public void addStock(Product product, Integer quantity)
            throws ProductException {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Integer newStock = product.getStock() + quantity;
        if (newStock > product.getStock()) {
            product = em.find(Product.class, product.getArticleNumber());
            product.setStock(newStock);
            em.merge(product);
        } else {
            tx.rollback();
            throw new ProductException("Addition wouldn't have increase stock, so was invalid..");
        }
        tx.commit();
    }

    public void removeStock(Product product, Integer quantity)
            throws ProductException {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        Integer newStock = product.getStock() - quantity;
        if (newStock >= 0) {
            product = em.find(Product.class, product.getArticleNumber());
            product.setStock(newStock);
            em.merge(product);
        } else {
            tx.rollback();
            throw new ProductException("Can't reduce stock to negative levels.");
        }
        tx.commit();
    }

    public void setStock(Product product, Integer newStock)
            throws ProductException {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        if (newStock >= 0) {
            // In case someone modified the class while we were editing newCount
            product = em.find(Product.class, product.getArticleNumber());
            product.setStock(newStock);
            em.merge(product);
        } else {
            tx.rollback();
            throw new ProductException("Can't set stock to negative levels.");
        }
        tx.commit();

    }

    // Seems preferable to refreshProduct
    public Product getProduct(Product product) {
        EntityManager em = emf.createEntityManager();
        System.out.println("Product => " + product);
        Product newProduct = em.find(Product.class, product.getArticleNumber());

        return newProduct;
    }

    // May need some more testing.
    public Product refreshProduct(Product product) {
        EntityManager em = emf.createEntityManager();
        //TypedQuery<Product> query = em.createNamedQuery(Constants.GET_PRODUCT, Product.class);
        //query.setParameter("idparam", product.getId());
        em.refresh(product);//em.find(Product.class, product.getArticleNumber());

        return product;
    }

    public List<Product> getProductsByName(String name) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> c = query.from(Product.class);
        ParameterExpression<String> param = crit.parameter(String.class);

        Expression<String> lowerCaseProductName = crit.lower(c.get(Product_.name));
        Expression<String> lowerCaseParam = crit.lower(param);
        //query.select(c).
        Expression<Boolean> nameRequirement = crit.equal(lowerCaseProductName, lowerCaseParam);
        Expression<Boolean> visibleRequirement = crit.isTrue(c.get(Product_.visible));
        query.select(c).where(crit.and(nameRequirement, visibleRequirement));
        // crit.equal and the lack of wildcards around name below 
        // is what makes this different from pattern searches

        TypedQuery<Product> finalQuery = em.createQuery(query);
        finalQuery.setParameter(param, name);
        List<Product> products = finalQuery.getResultList();
//        System.out.println("Fetched products => " + products);

        return products;
    }

    public List<Product> getProductsByNamePattern(String name) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> c = query.from(Product.class);
        ParameterExpression<String> param = crit.parameter(String.class);


        Expression<String> lowerCaseProductName = crit.lower(c.get(Product_.name));
        Expression<String> lowerCaseParam = crit.lower(param);
        Expression<Boolean> nameRequirement = crit.like(lowerCaseProductName, lowerCaseParam);
        Expression<Boolean> visibleRequirement = crit.isTrue(c.get(Product_.visible));
        query.select(c).where(crit.and(nameRequirement, visibleRequirement));

        TypedQuery<Product> finalQuery = em.createQuery(query);
        finalQuery.setParameter(param, "%" + name + "%"); // Ugly, but what you gonna do?
        List<Product> products = finalQuery.getResultList();
        //System.out.println("Fetched products => " + products);

        return products;
    }

    public List<Product> getProductsByManufacturer(String name) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> c = query.from(Product.class);
        ParameterExpression<String> param = crit.parameter(String.class);

        Expression<String> lowerCaseManufactName = crit.lower(c.get(Product_.manufacturer));
        Expression<String> lowerCaseParam = crit.lower(param);

        Expression<Boolean> nameRequirement = crit.equal(lowerCaseManufactName, lowerCaseParam);
        Expression<Boolean> visibleRequirement = crit.isTrue(c.get(Product_.visible));
        query.select(c).where(crit.and(nameRequirement, visibleRequirement));
        // crit.equal and the lack of wildcards around name below 
        // is what makes this different from pattern searches

        TypedQuery<Product> finalQuery = em.createQuery(query);
        finalQuery.setParameter(param, name);
        List<Product> products = finalQuery.getResultList();
        //System.out.println("Fetched products => " + products);

        return products;
    }

    public List<Product> getProductsByManufacturerPattern(String name) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> c = query.from(Product.class);
        ParameterExpression<String> param = crit.parameter(String.class);

        Expression<String> lowerCaseManufactName = crit.lower(c.get(Product_.manufacturer));
        Expression<String> lowerCaseParam = crit.lower(param);

        Expression<Boolean> nameRequirement = crit.like(lowerCaseManufactName, lowerCaseParam);
        Expression<Boolean> visibleRequirement = crit.isTrue(c.get(Product_.visible));
        query.select(c).where(crit.and(nameRequirement, visibleRequirement));

        TypedQuery<Product> finalQuery = em.createQuery(query);
        finalQuery.setParameter(param, "%" + name + "%"); // Still ugly
        List<Product> products = finalQuery.getResultList();
        //System.out.println("Fetched products => " + products);

        return products;
    }

    public List<Product> getProductsByCategory(Category category) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> fromProd = query.from(Product.class);
        //List<Expression> criteria = new ArrayList<Expression>(5);

        Expression vis = crit.isTrue(fromProd.get(Product_.visible));
        Expression rem = crit.isFalse(fromProd.get(Product_.removed));
        Expression criteria = crit.and(vis, rem);

        Path<Category> catField = fromProd.get(Product_.category);
        Expression cat = crit.equal(catField, category);
        for(Category c : category.getSubcategories()) {
            cat = crit.or(cat, crit.equal(catField, c));
        }
        criteria = crit.and(criteria, cat);

        query.select(fromProd).where(criteria);
        TypedQuery<Product> finalQuery = em.createQuery(query);
        try {
            List<Product> retVal = finalQuery.getResultList();
            for (Product p : retVal) {
                System.out.println("Returning product " + p);
            }
            return retVal;
        } catch (NoResultException e) {
            return new ArrayList<Product>();
        }
    }

    public List<Product> getAllProducts(Boolean evenNotVisible) {
        EntityManager em = emf.createEntityManager();
        CriteriaBuilder crit = em.getCriteriaBuilder();
        CriteriaQuery<Product> query = crit.createQuery(Product.class);
        Root<Product> fromProd = query.from(Product.class);
        if (evenNotVisible) {
            query = query.select(fromProd);
        } else {
            query = query.select(fromProd).where(crit.isTrue(fromProd.get(Product_.visible)));
        }

        TypedQuery<Product> finalQuery = em.createQuery(query);
        try {
            List<Product> retVal = finalQuery.getResultList();
            for (Product p : retVal) {
                System.out.println("Returning product " + p);
            }
            return retVal;
        } catch (NoResultException e) {
            return new ArrayList<Product>();
        }
    }

    public void removeFromDisplay(Product product) {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        product.setRemoved(Boolean.TRUE);
        em.merge(product);
        tx.commit();
    }

    public void removeCompletely(Product product) {
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();
        product = em.merge(product);
        em.remove(product);
        tx.commit();
        System.out.println("Removed " + product + " from DB? " + !isPersisted(product));
    }

}
